package com.wmx.jdbc_template_app.utils;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ArrayUtil;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.datasource.DriverManagerDataSource;

import java.sql.*;
import java.util.Date;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 原生 jdbc 工具类
 * 1、操作的是 Oracle 数据库，其他类型的数据库不完全兼容。
 *
 * @author wangMaoXiong
 * @version 1.0
 * @date 2023/11/19 13:18
 */
@SuppressWarnings("Duplicates")
public class JdbcUtil {

    private static final Logger log = LoggerFactory.getLogger(JdbcUtil.class);

    private static String driver = "oracle.jdbc.driver.OracleDriver";
    private static String url = "jdbc:oracle:thin:@127.0.0.1:1521:ORCL";
    private static String user = "scott";
    private static String password = "scott";

    static {
        try {
            Class.forName(driver);
        } catch (ClassNotFoundException e) {
            throw new ExceptionInInitializerError(e);
        }
    }

    /**
     * 获取数据库连接 - 原始JDBC方式
     *
     * @return
     */
    public static Connection getConnection() throws SQLException {
        // return DriverManager.getConnection(url, user, password);

        Properties props = new Properties();
        props.setProperty("user", user);
        props.setProperty("password", password);
        // 设置可以获取表、字段的remarks描述信息
        props.setProperty("remarks", "true");
        props.setProperty("useInformationSchema", "true");
        return DriverManager.getConnection(url, props);
    }

    /**
     * 获取数据库连接 - Spring 框架方式
     *
     * @return
     * @throws SQLException
     */
    public static Connection getConnectionV2() throws SQLException {
        Properties props = new Properties();
        // 设置可以获取表、字段的remarks描述信息
        props.setProperty("remarks", "true");
        props.setProperty("useInformationSchema", "true");

        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName(JdbcUtil.driver);
        dataSource.setUrl(JdbcUtil.url);
        dataSource.setUsername(JdbcUtil.user);
        dataSource.setPassword(JdbcUtil.password);
        dataSource.setConnectionProperties(props);
        Connection conn = dataSource.getConnection();
        return conn;
    }

    /**
     * 关闭数据库连接
     *
     * @param connection
     * @param statement
     * @param resultSet
     */
    public static void release(Connection connection, Statement statement, ResultSet resultSet) {
        if (resultSet != null) {
            try {
                resultSet.close();
            } catch (SQLException e) {
                log.error(ExceptionUtils.getStackTrace(e));
            }
        }
        if (statement != null) {
            try {
                statement.close();
            } catch (SQLException e) {
                log.error(ExceptionUtils.getStackTrace(e));
            }
        }
        if (connection != null) {
            try {
                connection.close();
            } catch (SQLException e) {
                log.error(ExceptionUtils.getStackTrace(e));
            }
        }
    }

    /**
     * 查询单条数据
     *
     * @param sql : 查询Sql——不支持参数预编译。
     *            不推荐将参数拼接在sql中，推荐使用占位符SQL预编译 {@link JdbcUtil#getOne(java.lang.String, java.lang.Object[])}
     * @return
     * @throws SQLException
     */
    public static Map<String, Object> getOne(String sql) throws SQLException {
        Map<String, Object> resultMap = new HashMap<>(64);
        Connection conn = null;
        PreparedStatement stmt = null;
        ResultSet resultSet = null;
        try {
            conn = JdbcUtil.getConnection();
            stmt = conn.prepareStatement(sql);
            resultSet = stmt.executeQuery();
            List<Map<String, Object>> resultSetToList = JdbcUtil.resultSetToList(resultSet);
            resultMap = resultSetToList.size() <= 0 ? resultMap : resultSetToList.get(0);
        } finally {
            JdbcUtil.release(conn, stmt, resultSet);
        }
        return resultMap;
    }

    /**
     * 查询单条数据
     *
     * @param sql    : 查询Sql——支持参数预编译
     * @param params ：SQL占位符对应的参数
     * @return
     * @throws SQLException
     */
    public static Map<String, Object> getOne(String sql, Object[] params) throws SQLException {
        Map<String, Object> resultMap = new HashMap<>(64);
        Connection conn = null;
        PreparedStatement stmt = null;
        ResultSet resultSet = null;
        try {
            conn = JdbcUtil.getConnection();
            stmt = conn.prepareStatement(sql);
            for (int i = 0; i < params.length; i++) {
                // SQL占位符索引从1开始
                stmt.setObject(i + 1, params[i]);
            }
            resultSet = stmt.executeQuery();
            List<Map<String, Object>> list = JdbcUtil.resultSetToList(resultSet);
            resultMap = list.size() <= 0 ? resultMap : list.get(0);
        } finally {
            JdbcUtil.release(conn, stmt, resultSet);
        }
        return resultMap;
    }

    /**
     * 查询多条数据
     *
     * @param sql : 查询Sql——不支持参数预编译
     *            不推荐将参数拼接在sql中，推荐使用占位符SQL预编译 {@link JdbcUtil#queryForList(java.lang.String, java.lang.Object[])}
     * @return
     * @throws SQLException
     */
    public static List<Map<String, Object>> queryForList(String sql) throws SQLException {
        List<Map<String, Object>> resultList;
        Connection conn = null;
        PreparedStatement stmt = null;
        ResultSet resultSet = null;
        try {
            conn = JdbcUtil.getConnectionV2();
            stmt = conn.prepareStatement(sql);
            resultSet = stmt.executeQuery();
            resultList = JdbcUtil.resultSetToList(resultSet);
        } finally {
            JdbcUtil.release(conn, stmt, resultSet);
        }
        return resultList;
    }

    /**
     * 查询多条数据
     *
     * @param sql    : 查询Sql——支持参数预编译
     * @param params ： SQL占位符对应的参数
     * @return
     * @throws SQLException
     */
    public static List<Map<String, Object>> queryForList(String sql, Object[] params) throws SQLException {
        List<Map<String, Object>> resultList;
        Connection conn = null;
        PreparedStatement stmt = null;
        ResultSet resultSet = null;
        try {
            conn = JdbcUtil.getConnectionV2();
            stmt = conn.prepareStatement(sql);
            for (int i = 0; i < params.length; i++) {
                // SQL占位符索引从1开始
                stmt.setObject(i + 1, params[i]);
            }
            resultSet = stmt.executeQuery();
            resultList = JdbcUtil.resultSetToList(resultSet);
        } finally {
            JdbcUtil.release(conn, stmt, resultSet);
        }
        return resultList;
    }

    /**
     * 分页查询多条数据 - Oracle 分页
     *
     * @param sql      : 查询Sql——支持参数预编译
     * @param params   ： SQL占位符对应的参数
     * @param pageNo   ：页码，从1开始
     * @param pageSize ：没页展示的条数
     * @return
     * @throws SQLException
     */
    public static List<Map<String, Object>> pageList(String sql, Object[] params, int pageNo, int pageSize) throws SQLException {
        List<Map<String, Object>> resultList;
        Connection conn = null;
        PreparedStatement stmt = null;
        ResultSet resultSet = null;
        try {
            params = params == null ? new Object[]{} : params;
            int startIndex = ((pageNo - 1) * pageSize + 1);
            int endIndex = pageNo * pageSize;
            conn = JdbcUtil.getConnectionV2();
            sql = "SELECT * FROM (SELECT ROWNUM as Sqlpageid, p.* FROM (" + sql + ") p WHERE ROWNUM <= ?) WHERE Sqlpageid >= ? ";
            params = ArrayUtil.append(params, endIndex, startIndex);
            stmt = conn.prepareStatement(sql);
            for (int i = 0; i < params.length; i++) {
                // SQL占位符索引从1开始
                stmt.setObject(i + 1, params[i]);
            }
            resultSet = stmt.executeQuery();
            resultList = JdbcUtil.resultSetToList(resultSet);
        } finally {
            JdbcUtil.release(conn, stmt, resultSet);
        }
        return resultList;
    }

    /**
     * 获取表或者视图列的完整信息：包括 列名、类型、长度、小数位数、是否允许为空 等等。
     * 1、采用代码动态获取时，针对不同的数据库，就需要代码自己兼容。
     * 2、另一种方式时，在数据库中新建视图，专门查询 表、列、类型、长度等等信息，代码中直接查询此视图就行。
     * * 此时针对不同数据库，只要灵活维护视图就行，代码基本不需要改。
     * 3、具体使用什么方式，还是得看实际需求。
     * <pre>
     *  返回值示例：
     *  [
     *  {"columnName":"JOB","columnType":12,"columnClassName":"java.lang.String","columnLabel":"JOB","columnTypeName":"VARCHAR2","columnDisplaySize":9,"precision":9,"scale":0,"isAutoIncrement":false,"isCaseSensitive":true,"isCurrency":false,"isNullable":1,"columnFullName":"JOB VARCHAR2 (9)"},
     *  {"columnName":"MGR","columnType":2,"columnClassName":"java.math.BigDecimal","columnLabel":"MGR","columnTypeName":"NUMBER","columnDisplaySize":5,"precision":4,"scale":0,"isAutoIncrement":false,"isCaseSensitive":false,"isCurrency":true,"isNullable":1,"columnFullName":"MGR NUMBER (4)"},
     *  {"columnName":"HIREDATE","columnType":93,"columnClassName":"java.sql.Timestamp","columnLabel":"HIREDATE","columnTypeName":"DATE","columnDisplaySize":7,"precision":0,"scale":0,"isAutoIncrement":false,"isCaseSensitive":false,"isCurrency":false,"isNullable":1,"columnFullName":"HIREDATE DATE"}
     * ]
     * </pre>
     *
     * @param objName ：表名称、视图名。如 emp、vw_emp
     * @return
     */
    public static List<Map<String, Object>> getColumnFullAttrs(String objName) throws SQLException {
        Connection conn = null;
        PreparedStatement stmt = null;
        ResultSet resultSet = null;
        List<Map<String, Object>> columns = new ArrayList<>();
        Map<String, Object> columnMap;
        try {
            String sql = "SELECT * FROM " + objName + " WHERE 1=2";
            conn = JdbcUtil.getConnection();
            stmt = conn.prepareStatement(sql);
            resultSet = stmt.executeQuery();
            ResultSetMetaData resultSetMetaData = resultSet.getMetaData();
            int columnCount = resultSetMetaData.getColumnCount();
            for (int i = 0; i < columnCount; i++) {
                columnMap = new LinkedHashMap<>(16);
                // 获取指定列的表名。列索引从1开始。如果不适用，则返回空""
                // columnMap.put("tableName", resultSetMetaData.getTableName(i + 1));
                // 获取指定列的表的目录名称。列索引从1开始。如果不适用，则返回空""
                // columnMap.put("catalogName", resultSetMetaData.getCatalogName(i + 1));
                // 获取指定列的表的架构。如果不适用，则返回空""
                // columnMap.put("schemaName", resultSetMetaData.getSchemaName(i + 1));
                // 获取数据库字段名称 (如 mgr、job、sal)
                columnMap.put("columnName", resultSetMetaData.getColumnName(i + 1));
                // 检索指定列的SQL类型。来自java.SQL.Types的SQL类型。如 2 表示 NUMBER、12 表示 VARCHAR2、93 表示 DATE。
                columnMap.put("columnType", resultSetMetaData.getColumnType(i + 1));
                // 返回Java类的完全限定名称(如 java.lang.String、java.sql.Timestamp、java.math.BigDecimal、oracle.sql.ROWID)。
                columnMap.put("columnClassName", resultSetMetaData.getColumnClassName(i + 1));
                // 获取指定列的 as 别名，以便在打印输出和显示中使用。如果未指定，则返回的值将与 getColumnName 方法返回的值相同。
                columnMap.put("columnLabel", resultSetMetaData.getColumnLabel(i + 1));
                // 获取数据库字段数据类型(如 NUMBER、VARCHAR2、DATE)。如果列类型是用户定义的类型，则返回完全限定的类型名。
                columnMap.put("columnTypeName", resultSetMetaData.getColumnTypeName(i + 1));
                // 获取列的正常最大宽度（以字符为单位）。
                columnMap.put("columnDisplaySize", resultSetMetaData.getColumnDisplaySize(i + 1));
                // 获取列的大小。
                // 对于数字数据，这是小数点左边的位数。
                // 对于字符数据，这是以字符为单位的长度。
                // 对于日期时间数据，这是字符串表示的长度（以字符为单位）（假设分数秒组件的最大允许精度）。
                // 对于二进制数据，这是以字节为单位的长度。
                // 对于ROWID数据类型，这是以字节为单位的长度。
                // 对于列大小不适用的数据类型，将返回0。
                columnMap.put("precision", resultSetMetaData.getPrecision(i + 1));
                // 获取指定列的小数点右侧的位数。对于小数位数不适用的数据类型，将返回0。
                columnMap.put("scale", resultSetMetaData.getScale(i + 1));
                // 指示指定列是否自动编号。true 或者 false。
                columnMap.put("isAutoIncrement", resultSetMetaData.isAutoIncrement(i + 1));
                // 指示列的大小写是否重要。true 或者 false。
                columnMap.put("isCaseSensitive", resultSetMetaData.isCaseSensitive(i + 1));
                // 指示指定列是否为现金值/金额。true 或者 false。
                columnMap.put("isCurrency", resultSetMetaData.isCurrency(i + 1));
                // 指示列是否允许为空. 0-不允许为空，1-允许为空。
                columnMap.put("isNullable", resultSetMetaData.isNullable(i + 1));
                // 拼接完整的列名，如 SAL NUMBER(7,2)
                String columnFullName = resultSetMetaData.getColumnName(i + 1) + " " + resultSetMetaData.getColumnTypeName(i + 1);
                if (resultSetMetaData.getPrecision(i + 1) > 0) {
                    columnFullName += " (" + resultSetMetaData.getPrecision(i + 1);
                    if (resultSetMetaData.getScale(i + 1) > 0) {
                        columnFullName += "," + resultSetMetaData.getScale(i + 1);
                    }
                    columnFullName += ")";
                }
                columnMap.put("columnFullName", columnFullName);
                columns.add(columnMap);
            }
        } finally {
            JdbcUtil.release(conn, stmt, resultSet);
        }
        return columns;
    }

    /**
     * 获取 catalog
     * 亲测 Oracle 数据库时查询不到结果。
     */
    public List<Map<String, Object>> getCatalogs() throws SQLException {
        List<Map<String, Object>> allCatalogs;
        Connection conn = null;
        ResultSet resultSet = null;
        try {
            DatabaseMetaData metaData = conn.getMetaData();
            resultSet = metaData.getCatalogs();
            allCatalogs = JdbcUtil.resultSetToList(resultSet);
        } finally {
            JdbcUtil.release(conn, null, resultSet);
        }
        return allCatalogs;
    }

    /**
     * 获取全部数据库名称(Oracle数据库时返回的全部用户名称)
     * <pre>
     * [
     *  {"TABLE_CATALOG":null,"TABLE_SCHEM":"SCOTT"},
     *  {"TABLE_CATALOG":null,"TABLE_SCHEM":"SYS"},
     *  {"TABLE_CATALOG":null,"TABLE_SCHEM":"SYSMAN"}
     * ]
     * </pre>
     *
     * @return : 其中的"TABLE_SCHEM"值就是数据库名称或者Oracle的用户名称
     */
    public static List<Map<String, Object>> getAllDatabases() throws SQLException {
        List<Map<String, Object>> allDatabases;
        Connection conn = null;
        ResultSet resultSet = null;
        try {
            conn = JdbcUtil.getConnection();
            DatabaseMetaData metaData = conn.getMetaData();
            resultSet = metaData.getSchemas();
            // 其中的"TABLE_SCHEM"值就是数据库名称或者Oracle的用户名称
            allDatabases = JdbcUtil.resultSetToList(resultSet);
        } finally {
            JdbcUtil.release(conn, null, resultSet);
        }
        // 其中的"TABLE_SCHEM"值就是数据库名称或者Oracle的用户名称
        allDatabases = allDatabases.stream().sorted(Comparator.comparing(item -> String.valueOf(item.get("TABLE_SCHEM")))).collect(Collectors.toList());
        return allDatabases;
    }

    /**
     * 获取指定数据库或者用户下面的表与视图
     * <pre>
     * [
     *  {"TABLE_CAT":null,"TABLE_NAME":"DEPT","TABLE_TYPE":"TABLE","REMARKS":"部门表","TABLE_SCHEM":"SCOTT"},
     *  {"TABLE_CAT":null,"TABLE_NAME":"EMP","TABLE_TYPE":"TABLE","REMARKS":"员工表","TABLE_SCHEM":"SCOTT"},
     *  {"TABLE_CAT":null,"TABLE_NAME":"VW_DEPT","TABLE_TYPE":"VIEW","REMARKS":null,"TABLE_SCHEM":"SCOTT"}
     * ]
     * </pre>
     *
     * @param database 数据库名称、或者Oracle的用户名称， 如 scott。不存在时不会报错，只是返回结果为空。
     * @return ：其中的 TABLE_NAME 是表/视图名称；TABLE_TYPE 是类型(TABLE、VIEW)；TABLE_SCHEM 是数据库或者用户名称
     */
    public static List<Map<String, Object>> getTablesByDatabase(String database) throws SQLException {
        List<Map<String, Object>> tables;
        Connection conn = null;
        ResultSet resultSet = null;
        try {
            conn = JdbcUtil.getConnectionV2();
            DatabaseMetaData metaData = conn.getMetaData();
            // 其中"%"表示任意/所有的意思
            resultSet = metaData.getTables(conn.getCatalog(), database.toUpperCase(), "%", new String[]{"TABLE", "VIEW"});
            // 返回值中的 TABLE_NAME 是表/视图名称
            tables = JdbcUtil.resultSetToList(resultSet);
        } finally {
            JdbcUtil.release(conn, null, resultSet);
        }
        // 其中的 TABLE_NAME 是表/视图名称
        tables = tables.stream().sorted(Comparator.comparing(item -> String.valueOf(item.get("TABLE_NAME")))).collect(Collectors.toList());
        return tables;
    }

    /**
     * 获取表或者视图列的完整信息：包括 列名、类型、长度、小数位数、是否允许为空 等等。
     * 1、采用代码动态获取时，针对不同的数据库，就需要代码自己兼容。
     * 2、另一种方式时，在数据库中新建视图，专门查询 表、列、类型、长度等等信息，代码中直接查询此视图就行。
     * * 此时针对不同数据库，只要灵活维护视图就行，代码基本不需要改。
     * 3、具体使用什么方式，还是得看实际需求。
     * <pre>
     *  返回值示例：
     *  [
     * {"char_octet_length":22,"type_name":"NUMBER","scope_schema":null,"is_nullable":"NO","is_autoincrement":"NO","nullable":0,"num_prec_radix":10,"scope_catalog":null,"decimal_digits":0,"sql_data_type":0,"column_name":"EMPNO","table_name":"EMP","source_data_type":null,"ordinal_position":1,"is_generatedcolumn":null,"table_schem":"SCOTT","column_size":4,"scope_table":null,"data_type":2,"column_def":null,"buffer_length":0,"sql_datetime_sub":0,"table_cat":null,"remarks":"员工编号"},
     * {"char_octet_length":10,"type_name":"VARCHAR2","scope_schema":null,"is_nullable":"YES","is_autoincrement":"NO","nullable":1,"num_prec_radix":10,"scope_catalog":null,"decimal_digits":null,"sql_data_type":0,"column_name":"ENAME","table_name":"EMP","source_data_type":null,"ordinal_position":2,"is_generatedcolumn":null,"table_schem":"SCOTT","column_size":10,"scope_table":null,"data_type":12,"column_def":null,"buffer_length":0,"sql_datetime_sub":0,"table_cat":null,"remarks":"员工姓名"},
     * {"char_octet_length":7,"type_name":"DATE","scope_schema":null,"is_nullable":"YES","is_autoincrement":"NO","nullable":1,"num_prec_radix":10,"scope_catalog":null,"decimal_digits":null,"sql_data_type":0,"column_name":"HIREDATE","table_name":"EMP","source_data_type":null,"ordinal_position":5,"is_generatedcolumn":null,"table_schem":"SCOTT","column_size":7,"scope_table":null,"data_type":93,"column_def":null,"buffer_length":0,"sql_datetime_sub":0,"table_cat":null,"remarks":"入职日期"},
     * {"char_octet_length":22,"type_name":"NUMBER","scope_schema":null,"is_nullable":"YES","is_autoincrement":"NO","nullable":1,"num_prec_radix":10,"scope_catalog":null,"decimal_digits":2,"sql_data_type":0,"column_name":"SAL","table_name":"EMP","source_data_type":null,"ordinal_position":6,"is_generatedcolumn":null,"table_schem":"SCOTT","column_size":7,"scope_table":null,"data_type":2,"column_def":null,"buffer_length":0,"sql_datetime_sub":0,"table_cat":null,"remarks":"薪水"}
     * ]
     * </pre>
     *
     * @param tableName ：表名称、视图名。如 emp、vw_emp。不存在时不会报错，只是返回结果为空。
     * @return
     */
    public static List<Map<String, Object>> getColumns(String tableName) throws SQLException {
        List<Map<String, Object>> columns;
        Connection conn = null;
        ResultSet resultSet = null;
        try {
            conn = JdbcUtil.getConnection();
            DatabaseMetaData metaData = conn.getMetaData();
            // 其中"%"表示任意/所有的意思
            resultSet = metaData.getColumns(conn.getCatalog(), null, tableName.toUpperCase(), "%");
            // TABLE_SCHEM：数据库名称或者用户名称
            // TABLE_NAME：表名
            // COLUMN_NAME：字段名称,如 sal、comm
            // TYPE_NAME：字段数据类型，如 NUMBER、VARCHAR2、DATE、ROWID
            // IS_NULLABLE：是否允许为空，NO-否、YES-是
            // NULLABLE：是否允许为空，0-否、1-是
            // REMARKS：字段描述
            // IS_AUTOINCREMENT：值是否自动递增，NO-否、YES-是
            // ORDINAL_POSITION：字段在表中的顺序位置.
            // DATA_TYPE：数字表示的数据类型。如 2 表示 NUMBER、12 表示 VARCHAR2、93 表示 DATE。
            // COLUMN_SIZE：字段长度。如 NUMBER(4) -> 4，VARCHAR2(10) -> 10，DATE -> 7，NUMBER(7,2) -> 7
            // DECIMAL_DIGITS：小数位数。如 NUMBER(4) -> 0，VARCHAR2(10) -> null，DATE -> null，NUMBER(7,2) -> 2
            columns = JdbcUtil.resultSetToList(resultSet);
        } finally {
            JdbcUtil.release(conn, null, resultSet);
        }
        return columns;
    }

    /**
     * 获取表的主键信息
     * <pre>
     * [
     *  {"column_name":"EMPNO","pk_name":"EMP_PK","table_cat":null,"table_schem":"SCOTT","table_name":"EMP","key_seq":1}
     * ]
     * </pre>
     *
     * @param tableName ：表名称，如 emp。
     * @return
     * @throws SQLException
     */
    public static List<Map<String, Object>> getPrimaryKeys(String tableName) throws SQLException {
        List<Map<String, Object>> columns;
        Connection conn = null;
        ResultSet resultSet = null;
        try {
            conn = JdbcUtil.getConnection();
            DatabaseMetaData metaData = conn.getMetaData();
            resultSet = metaData.getPrimaryKeys(conn.getCatalog(), null, tableName.toUpperCase());
            // column_name string => 列名称，如 EMPNO
            // pk_name string => 主键名称，如 EMP_PK
            // table_cat string => 表类别
            // table_schem string => 数据库名称或者oracle的用户名称，如 SCOTT
            // table_name string => 表名称,如 EMP
            // key_seq short => 主键中的序列号（值 1 表示主键中的第一列，值 2 表示主键中的第二列）。因为联合主键是多列组合成一个主键。
            columns = JdbcUtil.resultSetToList(resultSet);
        } finally {
            JdbcUtil.release(conn, null, resultSet);
        }
        return columns;
    }

    /**
     * 获取表的外键信息
     * <pre>
     * [
     *    {
     * 		"update_rule": null,
     * 		"deferrability": 7,
     * 		"fktable_schem": "SCOTT",
     * 		"fk_name": "EMP_FK_DEPT",
     * 		"fktable_name": "EMP",
     * 		"fktable_cat": null,
     * 		"delete_rule": 1,
     * 		"pk_name": "PK_DEPT",
     * 		"pktable_cat": null,
     * 		"key_seq": 1,
     * 		"pktable_name": "DEPT",
     * 		"pkcolumn_name": "DEPTNO",
     * 		"pktable_schem": "SCOTT",
     * 		"fkcolumn_name": "DEPTNO"
     *    }
     * ]
     * </pre>
     *
     * @param tableName ：表名称，如 emp。
     * @return
     * @throws SQLException
     */
    public static List<Map<String, Object>> getForeignKeys(String tableName) throws SQLException {
        List<Map<String, Object>> columns;
        Connection conn = null;
        ResultSet resultSet = null;
        try {
            conn = JdbcUtil.getConnection();
            DatabaseMetaData metaData = conn.getMetaData();
            resultSet = metaData.getImportedKeys(conn.getCatalog(), null, tableName.toUpperCase());
            // pktable_cat string => 被导入的主键表类别（可为 null）
            // pktable_schem string => 被导入的主键表模式（可为 null）
            // pktable_name string => 被导入的主键表名称
            // pkcolumn_name string => 被导入的主键列名称
            // fktable_cat string => 外键表类别（可为 null）
            // fktable_schem string => 外键表模式（可为 null）
            // fktable_name string => 外键表名称
            // fkcolumn_name string => 外键列名称
            // key_seq short => 外键中的序列号（值 1 表示外键中的第一列，值 2 表示外键中的第二列）。外键可以有多个。
            // update_rule short => 更新主键时外键发生的变化
            // delete_rule short => 删除主键时外键发生的变化
            // pk_name string => 主键的名称（可为 null）
            // fk_name string => 外键的名称（可为 null）
            // deferrability short => 是否可以将对外键约束的评估延迟到提交时间
            columns = JdbcUtil.resultSetToList(resultSet);
        } finally {
            JdbcUtil.release(conn, null, resultSet);
        }
        return columns;
    }

    /**
     * 获取数据库元信息(如 数据库版本)
     *
     * @return
     * @throws SQLException
     */
    public static Map<String, Object> getDatabaseProduct() throws SQLException {
        Map<String, Object> databaseProduct = new LinkedHashMap<>(8);
        Connection conn = null;
        try {
            conn = JdbcUtil.getConnection();
            DatabaseMetaData metaData = conn.getMetaData();
            // 获取数据库产品的名称，如 Oracle、Mysql
            String databaseProductName = metaData.getDatabaseProductName();
            // 数据库版本，如 Oracle Database 11g Enterprise Edition Release 11.2.0.1.0 - 64bit Production\nWith the Partitioning, OLAP, Data Mining and Real Application Testing options
            String databaseProductVersion = metaData.getDatabaseProductVersion();
            // 数据库主版本号，如 11
            int databaseMajorVersion = metaData.getDatabaseMajorVersion();
            // 数据库次版本号， 如 2
            int databaseMinorVersion = metaData.getDatabaseMinorVersion();
            // 驱动名称，如 Oracle JDBC driver
            String driverName = metaData.getDriverName();
            // 驱动版本，如 12.2.0.1.0
            String driverVersion = metaData.getDriverVersion();
            // 连接用户名称，如 SCOTT
            String userName = metaData.getUserName();
            // 数据库连接地址，如 jdbc:oracle:thin:@127.0.0.1:1521:ORCL
            String dbUrl = metaData.getURL();

            databaseProduct.put("databaseProductName", databaseProductName);
            databaseProduct.put("databaseProductVersion", databaseProductVersion);
            databaseProduct.put("databaseMajorVersion", databaseMajorVersion);
            databaseProduct.put("databaseMinorVersion", databaseMinorVersion);
            databaseProduct.put("driverName", driverName);
            databaseProduct.put("driverVersion", driverVersion);
            databaseProduct.put("userName", userName);
            databaseProduct.put("dbUrl", dbUrl);
        } finally {
            JdbcUtil.release(conn, null, null);
        }
        return databaseProduct;
    }

    /**
     * 将 resultSetToList 结果集转为 List<Map<String, Object>>
     *
     * @param resultSet ：没有查询到值时，不会为 null，只是游标 next 不会走，
     * @return ：返回的 map key(字段名称) 统一转为小写。
     * @throws SQLException
     */
    private static List<Map<String, Object>> resultSetToList(ResultSet resultSet) throws SQLException {
        List<Map<String, Object>> list = new ArrayList<>();
        // 结果集元数据对象，可以用于获取表的列信息，即使没有查询到值，列信息同样会有
        ResultSetMetaData resultSetMetaData = resultSet.getMetaData();
        int columnCount = resultSetMetaData.getColumnCount();
        while (resultSet.next()) {
            Map<String, Object> rowData = new LinkedHashMap<>(columnCount);
            for (int i = 1; i <= columnCount; i++) {
                Object object = resultSet.getObject(i);
                // 将时间格式化为"yyyy-MM-dd HH:mm:ss"
                if (object != null && object instanceof Date) {
                    object = DateUtil.formatDateTime((Date) object);
                }
                rowData.put(resultSetMetaData.getColumnName(i).toLowerCase(), object);
            }
            list.add(rowData);
        }
        return list;
    }

}
